#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
XML文件处理器

该模块提供了XML文件的读写和处理功能。

创建时间: 2023-07-12
作者: Python Tools Team
"""

import xml.etree.ElementTree as ET
from xml.dom import minidom
from typing import Optional, Union, Dict, Any, List, Tuple, Callable
from pathlib import Path
import io

from modules.logger import LogManager
from modules.exception import exception_handler, FileException
from modules.fileio.file_manager import FileManager


class XMLFileHandler:
    """
    XML文件处理器
    
    该类提供了XML文件的读写和处理功能，支持多种格式和选项。
    """
    
    def __init__(self, file_manager: Optional[FileManager] = None, logger=None):
        """
        初始化XML文件处理器
        
        Args:
            file_manager: 文件管理器实例，如果为None则创建新实例
            logger: 日志记录器，如果为None则使用默认日志记录器
        """
        self.file_manager = file_manager or FileManager()
        self.logger = logger or LogManager.get_both_logger(name="xml_handler")
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def read(self, file_path: Union[str, Path], encoding: str = 'utf-8') -> ET.Element:
        """
        读取XML文件
        
        Args:
            file_path: XML文件路径
            encoding: 文件编码，默认为utf-8
        
        Returns:
            ET.Element: XML根元素
        
        Raises:
            FileException: 文件不存在或读取失败时抛出
            ET.ParseError: XML解析失败时抛出
        
        Examples:
            >>> xml_handler = XMLFileHandler()
            >>> root = xml_handler.read('example.xml')
            >>> print(root.tag)
        """
        file_path = Path(file_path)
        if not file_path.exists():
            raise FileException(f"文件不存在: {file_path}")
        
        content = self.file_manager.read_text(file_path, encoding=encoding)
        
        try:
            root = ET.fromstring(content)
        except ET.ParseError as e:
            self.logger.error(f"XML解析失败: {e}")
            raise
        
        self.logger.debug(f"已读取XML文件: {file_path}")
        return root
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def read_as_tree(self, file_path: Union[str, Path], encoding: str = 'utf-8') -> ET.ElementTree:
        """
        读取XML文件为ElementTree
        
        Args:
            file_path: XML文件路径
            encoding: 文件编码，默认为utf-8
        
        Returns:
            ET.ElementTree: XML元素树
        
        Raises:
            FileException: 文件不存在或读取失败时抛出
            ET.ParseError: XML解析失败时抛出
        
        Examples:
            >>> xml_handler = XMLFileHandler()
            >>> tree = xml_handler.read_as_tree('example.xml')
            >>> root = tree.getroot()
            >>> print(root.tag)
        """
        file_path = Path(file_path)
        if not file_path.exists():
            raise FileException(f"文件不存在: {file_path}")
        
        try:
            tree = ET.parse(file_path)
        except ET.ParseError as e:
            self.logger.error(f"XML解析失败: {e}")
            raise
        except Exception as e:
            raise FileException(f"读取XML文件失败: {e}")
        
        self.logger.debug(f"已读取XML文件: {file_path}")
        return tree
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def write(self, file_path: Union[str, Path], root: ET.Element, encoding: str = 'utf-8', 
              xml_declaration: bool = True, pretty_print: bool = True, 
              create_dirs: bool = True) -> bool:
        """
        写入XML元素到文件
        
        Args:
            file_path: XML文件路径
            root: XML根元素
            encoding: 文件编码，默认为utf-8
            xml_declaration: 是否包含XML声明，默认为True
            pretty_print: 是否美化输出，默认为True
            create_dirs: 如果父目录不存在，是否创建，默认为True
        
        Returns:
            bool: 写入成功返回True
        
        Raises:
            FileException: 写入失败时抛出
        
        Examples:
            >>> xml_handler = XMLFileHandler()
            >>> root = ET.Element('root')
            >>> child = ET.SubElement(root, 'child')
            >>> child.text = 'Hello, World!'
            >>> xml_handler.write('example.xml', root)
        """
        file_path = Path(file_path)
        
        # 创建ElementTree
        tree = ET.ElementTree(root)
        
        # 转换为字符串
        if pretty_print:
            # 使用minidom美化输出
            rough_string = ET.tostring(root, encoding=encoding)
            reparsed = minidom.parseString(rough_string)
            xml_str = reparsed.toprettyxml(indent="  ", encoding=encoding)
            
            # 移除空行
            if isinstance(xml_str, bytes):
                xml_str = xml_str.decode(encoding)
            lines = [line for line in xml_str.split('\n') if line.strip()]
            xml_str = '\n'.join(lines)
            
            # 添加XML声明
            if xml_declaration and not xml_str.startswith('<?xml'):
                xml_str = f'<?xml version="1.0" encoding="{encoding}"?>\n{xml_str}'
        else:
            # 直接转换为字符串
            xml_str = ET.tostring(root, encoding=encoding, xml_declaration=xml_declaration)
            if isinstance(xml_str, bytes):
                xml_str = xml_str.decode(encoding)
        
        # 写入文件
        self.file_manager.write_text(file_path, xml_str, encoding=encoding, create_dirs=create_dirs)
        
        self.logger.debug(f"已写入XML文件: {file_path}")
        return True
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def write_tree(self, file_path: Union[str, Path], tree: ET.ElementTree, encoding: str = 'utf-8', 
                  xml_declaration: bool = True, pretty_print: bool = True, 
                  create_dirs: bool = True) -> bool:
        """
        写入XML元素树到文件
        
        Args:
            file_path: XML文件路径
            tree: XML元素树
            encoding: 文件编码，默认为utf-8
            xml_declaration: 是否包含XML声明，默认为True
            pretty_print: 是否美化输出，默认为True
            create_dirs: 如果父目录不存在，是否创建，默认为True
        
        Returns:
            bool: 写入成功返回True
        
        Raises:
            FileException: 写入失败时抛出
        
        Examples:
            >>> xml_handler = XMLFileHandler()
            >>> tree = ET.ElementTree(ET.Element('root'))
            >>> child = ET.SubElement(tree.getroot(), 'child')
            >>> child.text = 'Hello, World!'
            >>> xml_handler.write_tree('example.xml', tree)
        """
        return self.write(file_path, tree.getroot(), encoding, xml_declaration, pretty_print, create_dirs)
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def find_element(self, root: ET.Element, xpath: str) -> Optional[ET.Element]:
        """
        查找XML元素
        
        Args:
            root: XML根元素
            xpath: XPath表达式
        
        Returns:
            Optional[ET.Element]: 找到的元素，如果未找到则返回None
        
        Examples:
            >>> xml_handler = XMLFileHandler()
            >>> root = xml_handler.read('example.xml')
            >>> element = xml_handler.find_element(root, './child')
            >>> if element is not None:
            >>>     print(element.text)
        """
        return root.find(xpath)
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def find_all_elements(self, root: ET.Element, xpath: str) -> List[ET.Element]:
        """
        查找所有匹配的XML元素
        
        Args:
            root: XML根元素
            xpath: XPath表达式
        
        Returns:
            List[ET.Element]: 找到的元素列表
        
        Examples:
            >>> xml_handler = XMLFileHandler()
            >>> root = xml_handler.read('example.xml')
            >>> elements = xml_handler.find_all_elements(root, './child')
            >>> for element in elements:
            >>>     print(element.text)
        """
        return root.findall(xpath)
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def get_element_text(self, root: ET.Element, xpath: str, default: str = '') -> str:
        """
        获取XML元素的文本内容
        
        Args:
            root: XML根元素
            xpath: XPath表达式
            default: 如果元素不存在或文本为空，返回的默认值
        
        Returns:
            str: 元素的文本内容
        
        Examples:
            >>> xml_handler = XMLFileHandler()
            >>> root = xml_handler.read('example.xml')
            >>> text = xml_handler.get_element_text(root, './child')
            >>> print(text)
        """
        element = root.find(xpath)
        if element is not None and element.text is not None:
            return element.text
        return default
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def get_element_attribute(self, root: ET.Element, xpath: str, attribute: str, 
                            default: str = '') -> str:
        """
        获取XML元素的属性值
        
        Args:
            root: XML根元素
            xpath: XPath表达式
            attribute: 属性名
            default: 如果元素不存在或属性不存在，返回的默认值
        
        Returns:
            str: 属性值
        
        Examples:
            >>> xml_handler = XMLFileHandler()
            >>> root = xml_handler.read('example.xml')
            >>> attr = xml_handler.get_element_attribute(root, './child', 'id')
            >>> print(attr)
        """
        element = root.find(xpath)
        if element is not None and attribute in element.attrib:
            return element.attrib[attribute]
        return default
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def set_element_text(self, root: ET.Element, xpath: str, text: str, 
                        create_if_not_exists: bool = False, parent_xpath: str = '.') -> ET.Element:
        """
        设置XML元素的文本内容
        
        Args:
            root: XML根元素
            xpath: XPath表达式
            text: 要设置的文本内容
            create_if_not_exists: 如果元素不存在，是否创建，默认为False
            parent_xpath: 如果需要创建元素，父元素的XPath表达式，默认为根元素
        
        Returns:
            ET.Element: 设置文本的元素
        
        Raises:
            ValueError: 元素不存在且不允许创建时抛出
        
        Examples:
            >>> xml_handler = XMLFileHandler()
            >>> root = xml_handler.read('example.xml')
            >>> element = xml_handler.set_element_text(root, './child', 'New Text')
            >>> xml_handler.write('example.xml', root)
        """
        element = root.find(xpath)
        
        if element is None:
            if not create_if_not_exists:
                raise ValueError(f"元素不存在: {xpath}")
            
            # 创建元素
            parent = root.find(parent_xpath)
            if parent is None:
                parent = root
            
            # 获取最后一个标签名
            tag = xpath.split('/')[-1]
            if tag.startswith('@') or tag == '.' or tag == '..':
                raise ValueError(f"无效的标签名: {tag}")
            
            element = ET.SubElement(parent, tag)
        
        element.text = text
        return element
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def set_element_attribute(self, root: ET.Element, xpath: str, attribute: str, value: str, 
                             create_if_not_exists: bool = False, parent_xpath: str = '.') -> ET.Element:
        """
        设置XML元素的属性值
        
        Args:
            root: XML根元素
            xpath: XPath表达式
            attribute: 属性名
            value: 属性值
            create_if_not_exists: 如果元素不存在，是否创建，默认为False
            parent_xpath: 如果需要创建元素，父元素的XPath表达式，默认为根元素
        
        Returns:
            ET.Element: 设置属性的元素
        
        Raises:
            ValueError: 元素不存在且不允许创建时抛出
        
        Examples:
            >>> xml_handler = XMLFileHandler()
            >>> root = xml_handler.read('example.xml')
            >>> element = xml_handler.set_element_attribute(root, './child', 'id', '123')
            >>> xml_handler.write('example.xml', root)
        """
        element = root.find(xpath)
        
        if element is None:
            if not create_if_not_exists:
                raise ValueError(f"元素不存在: {xpath}")
            
            # 创建元素
            parent = root.find(parent_xpath)
            if parent is None:
                parent = root
            
            # 获取最后一个标签名
            tag = xpath.split('/')[-1]
            if tag.startswith('@') or tag == '.' or tag == '..':
                raise ValueError(f"无效的标签名: {tag}")
            
            element = ET.SubElement(parent, tag)
        
        element.set(attribute, value)
        return element
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def add_element(self, root: ET.Element, parent_xpath: str, tag: str, 
                   text: Optional[str] = None, attrib: Optional[Dict[str, str]] = None) -> ET.Element:
        """
        添加XML元素
        
        Args:
            root: XML根元素
            parent_xpath: 父元素的XPath表达式
            tag: 新元素的标签名
            text: 新元素的文本内容，默认为None
            attrib: 新元素的属性字典，默认为None
        
        Returns:
            ET.Element: 新添加的元素
        
        Raises:
            ValueError: 父元素不存在时抛出
        
        Examples:
            >>> xml_handler = XMLFileHandler()
            >>> root = xml_handler.read('example.xml')
            >>> new_element = xml_handler.add_element(root, '.', 'new_child', 'Hello', {'id': '123'})
            >>> xml_handler.write('example.xml', root)
        """
        parent = root if parent_xpath == '.' else root.find(parent_xpath)
        
        if parent is None:
            raise ValueError(f"父元素不存在: {parent_xpath}")
        
        # 创建新元素
        element = ET.SubElement(parent, tag, attrib or {})
        if text is not None:
            element.text = text
        
        return element
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def remove_element(self, root: ET.Element, xpath: str) -> bool:
        """
        移除XML元素
        
        Args:
            root: XML根元素
            xpath: 要移除元素的XPath表达式
        
        Returns:
            bool: 移除成功返回True，元素不存在返回False
        
        Examples:
            >>> xml_handler = XMLFileHandler()
            >>> root = xml_handler.read('example.xml')
            >>> success = xml_handler.remove_element(root, './child')
            >>> if success:
            >>>     xml_handler.write('example.xml', root)
        """
        element = root.find(xpath)
        if element is None:
            return False
        
        parent = root.find('..')
        if parent is None:
            # 如果是根元素，无法移除
            return False
        
        parent.remove(element)
        return True
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def to_dict(self, element: ET.Element, include_attributes: bool = True) -> Dict[str, Any]:
        """
        将XML元素转换为字典
        
        Args:
            element: XML元素
            include_attributes: 是否包含属性，默认为True
        
        Returns:
            Dict[str, Any]: 转换后的字典
        
        Examples:
            >>> xml_handler = XMLFileHandler()
            >>> root = xml_handler.read('example.xml')
            >>> data = xml_handler.to_dict(root)
            >>> print(data)
        """
        result = {}
        
        # 添加属性
        if include_attributes and element.attrib:
            result['@attributes'] = dict(element.attrib)
        
        # 添加文本内容
        if element.text and element.text.strip():
            result['#text'] = element.text.strip()
        
        # 处理子元素
        for child in element:
            child_dict = self.to_dict(child, include_attributes)
            
            if child.tag in result:
                # 如果已存在同名子元素，转换为列表
                if not isinstance(result[child.tag], list):
                    result[child.tag] = [result[child.tag]]
                result[child.tag].append(child_dict)
            else:
                result[child.tag] = child_dict
        
        return result
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def from_dict(self, data: Dict[str, Any], root_tag: str) -> ET.Element:
        """
        将字典转换为XML元素
        
        Args:
            data: 字典数据
            root_tag: 根元素标签名
        
        Returns:
            ET.Element: 转换后的XML元素
        
        Examples:
            >>> xml_handler = XMLFileHandler()
            >>> data = {
            >>>     '@attributes': {'id': '123'},
            >>>     '#text': 'Root Text',
            >>>     'child': {
            >>>         '#text': 'Child Text'
            >>>     }
            >>> }
            >>> root = xml_handler.from_dict(data, 'root')
            >>> xml_handler.write('example.xml', root)
        """
        root = ET.Element(root_tag)
        
        for key, value in data.items():
            if key == '@attributes':
                # 设置属性
                for attr_name, attr_value in value.items():
                    root.set(attr_name, str(attr_value))
            elif key == '#text':
                # 设置文本内容
                root.text = str(value)
            elif isinstance(value, dict):
                # 递归处理子元素
                child = self.from_dict(value, key)
                root.append(child)
            elif isinstance(value, list):
                # 处理列表
                for item in value:
                    if isinstance(item, dict):
                        child = self.from_dict(item, key)
                        root.append(child)
                    else:
                        # 简单值列表
                        child = ET.SubElement(root, key)
                        child.text = str(item)
            else:
                # 简单值
                child = ET.SubElement(root, key)
                child.text = str(value)
        
        return root
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def validate_with_xsd(self, xml_file_path: Union[str, Path], xsd_file_path: Union[str, Path], 
                         encoding: str = 'utf-8') -> Tuple[bool, Optional[str]]:
        """
        使用XSD验证XML文件
        
        Args:
            xml_file_path: XML文件路径
            xsd_file_path: XSD文件路径
            encoding: 文件编码，默认为utf-8
        
        Returns:
            Tuple[bool, Optional[str]]: (验证是否通过, 错误信息)
        
        Raises:
            FileException: 文件不存在或读取失败时抛出
        
        Examples:
            >>> xml_handler = XMLFileHandler()
            >>> valid, error = xml_handler.validate_with_xsd('example.xml', 'schema.xsd')
            >>> if valid:
            >>>     print('验证通过')
            >>> else:
            >>>     print(f'验证失败: {error}')
        """
        try:
            # 尝试导入lxml模块，如果不可用则抛出异常
            from lxml import etree
        except ImportError:
            raise ImportError("验证XML需要安装lxml模块，请使用'pip install lxml'安装")
        
        xml_file_path = Path(xml_file_path)
        xsd_file_path = Path(xsd_file_path)
        
        if not xml_file_path.exists():
            raise FileException(f"XML文件不存在: {xml_file_path}")
        
        if not xsd_file_path.exists():
            raise FileException(f"XSD文件不存在: {xsd_file_path}")
        
        try:
            # 读取XSD文件
            xsd_content = self.file_manager.read_text(xsd_file_path, encoding=encoding)
            xsd_root = etree.XML(xsd_content.encode(encoding))
            xsd_schema = etree.XMLSchema(xsd_root)
            
            # 读取XML文件
            xml_content = self.file_manager.read_text(xml_file_path, encoding=encoding)
            xml_root = etree.XML(xml_content.encode(encoding))
            
            # 验证XML
            xsd_schema.assertValid(xml_root)
            return True, None
        except etree.XMLSyntaxError as e:
            return False, f"XML语法错误: {e}"
        except etree.DocumentInvalid as e:
            return False, f"文档无效: {e}"
        except Exception as e:
            return False, f"验证失败: {e}"